#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include "Scanning.h"
#include "Scanner.h"
#include "../BMC.h"
#include "../../List.h"
#include "../../JRYNES.h"
#include "../Translation/Translation.h"
#include "../Translation/ParallelCompiler.h"

extern EmulationSettings Settings;

typedef enum{
    PS_Sleeping,
    PS_Working
} PSState;

typedef struct ParallelScanningInfo{
    pthread_mutex_t M;
    pthread_cond_t  Active;
    pthread_t ID;
    List* ParallelScanningList;
    PSState State;
} ParallelScanningInfo;

static ParallelScanningInfo PS;

void SNN_Init(){
    SCN_Init();
}

//This function is reponsible for updating the BMCRepository with the new BMC
//Information
static void UpdateBMCRepository(BMCList* List){
    uint32_t NBlocks, i;
    
    NBlocks = BMCLNumBlocks(List);
    
    for(i = 0; i < NBlocks; i++)
        BMCRRefresh(BMCLGetBlock(List, i), 1);
}

BMCList* SNN_DoScanning(uint16_t Addr){
    BMCList *BMCs;
    BMC* Block;
    uint32_t i, NB;
    
    BMCs = SCN_Scan(Addr);
    
    NB = BMCLNumBlocks(BMCs);
    
    if(Settings.ArgumentFlags & AF_OPT_BLOCK_COALESCING)
        SCN_BlockCoalescing(BMCs);
    
    for(i = 0; i < NB; i++)
        SCN_BMCAnalysis(BMCs->Blocks[i]);
    
    UpdateBMCRepository(BMCs);
    
    Block = LST_Dequeue(BMCs->AnalyzeBlocks);
    while(Block){
        SCN_BMCAnalysis(Block);
        Block = LST_Dequeue(BMCs->AnalyzeBlocks);
    }    
    
    return BMCs;
}

static void SNN_ParallelScanningAdd(uint16_t Addr){
    uint32_t *TargetAddr;
    
    TargetAddr = malloc(sizeof(uint32_t));
    
    *TargetAddr = Addr;
    
    pthread_mutex_lock(&(PS.M));
    LST_Push(PS.ParallelScanningList, TargetAddr);
    
    if(PS.State == PS_Sleeping){
        //PS.State = PS_Working;
        pthread_cond_signal(&(PS.Active));
    }
    pthread_mutex_unlock(&(PS.M));
    
}

static uint32_t* SNN_ParallelScanningCheckout(){
    uint32_t *TargetAddr;
    
    pthread_mutex_lock(&(PS.M));
    TargetAddr = LST_Pop(PS.ParallelScanningList);
    pthread_mutex_unlock(&(PS.M));
    
    return TargetAddr;
}

static void* SNN_ParallelScanningThread(void* Args){
    uint32_t *TargetAddr;
    BMCList *BMCs;
    
    PS.State = PS_Sleeping;
    
    while(1){
        
        switch(PS.State){
            
            case PS_Sleeping:
                //printf("Scanner sleeping...\n");
                pthread_mutex_lock(&(PS.M));
                
                    pthread_cond_wait(&(PS.Active), &(PS.M));
                    pthread_cond_init(&(PS.Active), NULL);
                    
                    PS.State = PS_Working;
                    
                pthread_mutex_unlock(&(PS.M));
                
                //printf("Scanner active!...\n");
            break;
         
            case PS_Working:
                TargetAddr = SNN_ParallelScanningCheckout();
                if(TargetAddr){
                    BMCs = SNN_DoScanning(*TargetAddr);
                    PC_Compile(BMCs);
                    free(TargetAddr);
                }
                else
                    PS.State = PS_Sleeping;
            break;
            
        }
        
    }
    
}

void SNN_ParallelScanningInit(void){
    PS.ParallelScanningList = LST_New(NULL);
    pthread_mutex_init(&(PS.M), NULL);
    pthread_cond_init(&(PS.Active), NULL);
    
    pthread_create(&(PS.ID), NULL, SNN_ParallelScanningThread, NULL);
}

void SNN_InstructionFault(uint16_t Addr){
    BMCList *BMCs;
    
    if(Settings.ArgumentFlags & AF_DBT_PARALLEL_COMPILATION){
        SNN_ParallelScanningAdd(Addr);
    }
    else{
        BMCs = SNN_DoScanning(Addr);
        TRL_TranslationRequest(BMCs);
    }
}
